package com.pureland.boot.ext;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pureland.common.entity.R;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpMethod;
import org.springframework.util.DigestUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * @projectName: pureland_cloud
 * @className: com.pureland.boot.config.ext.NoRepeatSubmitAspect
 * @description: 基于Redis表单防重复提交AOP
 * @author: tong.li
 * @createTime: 2020/10/13 10:25
 * @version: v1.0
 * @copyright: 版权所有 李彤 © 2020
 */
@Aspect
@Slf4j
public class NoRepeatSubmitAspect {

    @Autowired
    private StringRedisTemplate template;


    @Pointcut("@annotation(com.pureland.boot.ext.NoRepeatSubmit)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object interceptor(ProceedingJoinPoint pjp) {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            String sessionId = RequestContextHolder.getRequestAttributes().getSessionId();
            String method = request.getMethod();
            String servletPath = request.getServletPath();
            // 一般POST/PUT对表单重复提交校验有很强的要求,因为有添加/更新的复杂逻辑
            if (!HttpMethod.POST.toString().equalsIgnoreCase(method) && !HttpMethod.PUT.toString().equalsIgnoreCase(method)) {
                log.warn("警告:当前请求{}的请求方式{}不支持表单重复提交校验,请去掉@NoRepeatSubmit注解,瞎搞!",servletPath,method);
                return pjp.proceed();
            }
            ValueOperations<String, String> opsForValue = template.opsForValue();
            // 这里暂时先用sessionId,可以用系统全局唯一的认证token
            String key = sessionId + "-" + servletPath;
            NoRepeatSubmit annotation = ((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(NoRepeatSubmit.class);
            // 如果缓存中有这个url并且值一样视为重复提交
            String cacheValue = getCacheValue(pjp.getArgs());
            String redisValue = opsForValue.get(key);
            if (redisValue == null) {
                Object o = pjp.proceed();
                opsForValue.set(key, getCacheValue(pjp.getArgs()), annotation.duration(), annotation.unit());
                return o;
            } else if (redisValue != null && cacheValue.equals(redisValue)){
                return R.fail("操作过于频繁，请稍后重试");
            }
            return  pjp.proceed();
        } catch (RuntimeException e) {
            throw e;
        } catch (Throwable throwable) {
            throw new RuntimeException("服务器异常",throwable);
        }
    }

    /**
     * 加上用户的唯一标识
     */
    private String getCacheValue(Object[] args) throws JsonProcessingException {
        String source = "";
        if (args.length > 0) {
            ObjectMapper mapper = new ObjectMapper();
            source = mapper.writeValueAsString(args);
        }
        return DigestUtils.md5DigestAsHex(source.getBytes());
    }

}
